package com.entity.NeuralNetworks;

import com.util.Function.Constant;
import com.util.Function.Sigmoid;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Created by wlc on 2018/5/16.
 */
public class NeuralNetworks {
    /**
     * 数据成员
     */
    private ArrayList<BigInteger> inputNeuronId;   //存储纯输入神经元id的集合
    private HashMap<BigInteger,Neurons> NeuronCollection;   //存储神经元的集合[id,神经元]
    private ArrayList<BigInteger> outputNeuronId;   //存储纯输出神经元id的集合
    private Double learningRate;                   //神经网络学习率

    public NeuralNetworks(){
        inputNeuronId = new ArrayList<>();
        NeuronCollection = new HashMap<>();
        outputNeuronId = new ArrayList<>();
    }

    /**
     *数据成员设置和获取函数
     */
    public void setInputNeuronId(int index,BigInteger id){
        this.inputNeuronId.set(index,id);
    }

    public ArrayList<BigInteger> getInputNeuronId(){
        return this.inputNeuronId;
    }

    public void setNeuronCollection(BigInteger id,Neurons neurons){
        this.NeuronCollection.put(id,neurons);
    }

    public HashMap<BigInteger,Neurons> getNeuronCollection(){
        return this.NeuronCollection;
    }

    public void setOutputNeuronId(int index,BigInteger id){
        this.outputNeuronId.set(index,id);
    }

    public ArrayList<BigInteger> getOutputNeuronId(){
        return this.outputNeuronId;
    }

    /**
     * 根据突触ID连接神经元
     * @param id 突触ID
     */
    public void addSynapse(String id){
        String[] SynapseIds = id.split("-");

        Neurons neurons = this.NeuronCollection.get(new BigInteger(SynapseIds[0]));
        if (SynapseIds[1].equals("A")){
            neurons.setInputWeights(new BigInteger(SynapseIds[2]),1d);
            neurons.setInputValues(new BigInteger(SynapseIds[2]),0d);
            neurons.setInputDeviation(new BigInteger(SynapseIds[2]),0d);
        }else {
            neurons.setOutputs(new BigInteger(SynapseIds[2]));
        }
        this.NeuronCollection.put(new BigInteger(SynapseIds[0]),neurons);

        neurons = this.NeuronCollection.get(new BigInteger(SynapseIds[2]));
        if (SynapseIds[3].equals("A")){
            neurons.setInputWeights(new BigInteger(SynapseIds[0]),1d);
            neurons.setInputValues(new BigInteger(SynapseIds[0]),0d);
            neurons.setInputDeviation(new BigInteger(SynapseIds[0]),0d);
        }else {
            neurons.setOutputs(new BigInteger(SynapseIds[0]));
        }
        this.NeuronCollection.put(new BigInteger(SynapseIds[2]),neurons);
    }

    /**
     * 根据函数ID赋予相应神经元对应的激活函数
     * @param neuronsId 需要赋予激活函数的神经元ID
     * @param functionId 函数ID
     */
    public void addActivation(BigInteger neuronsId, String functionId){
        Neurons neurons = this.NeuronCollection.get(neuronsId);
        switch (functionId){
            case "function-constant":
                neurons.setActivationFunction(new Constant());
                break;
            case "function-sigmod":
                neurons.setActivationFunction(new Sigmoid());
                break;
        }
        this.NeuronCollection.put(neuronsId,neurons);
    }

    /**
     * 将神经元集合中用于接收输入数据，输出结果数据的
     * 神经元提取到相应hashmap中
     */
    public void extractInputOutput(){
        Iterator iterator = this.NeuronCollection.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Neurons neurons = (Neurons) entry.getValue();
            if (neurons.getInputWeights().isEmpty()){
                this.inputNeuronId.add(neurons.getId());
            }else if (neurons.getOutputs().isEmpty()){
                this.outputNeuronId.add(neurons.getId());
            }
        }
    }

    /**
     * 已知神经网络输入层各神经元输出的条件下，递归计算
     * 其余所有神经元的输入输出
     */
    public void computeOutValue(){
        for (BigInteger id : this.inputNeuronId){
            forwardRecursive(NeuronCollection.get(id));
        }
    }

    private void forwardRecursive(Neurons neurons){
        for (BigInteger outId : neurons.getOutputs()){
            Neurons nextNeurons;
            Double inputValue;
            nextNeurons = this.NeuronCollection.get(outId);
            inputValue = nextNeurons.getInputWeights().get(neurons.getId()) * neurons.getOutputValue();
            nextNeurons.setInputValues(neurons.getId(),inputValue);
            nextNeurons.computeOutput();
            this.NeuronCollection.put(outId,nextNeurons);
            forwardRecursive(nextNeurons);
        }
    }

    /**
     * 已知神经网络输出层各神经元偏差的条件下，递归计算
     * 其余所有神经元的偏差
     */
    public void computeDeviation(){
        for (BigInteger id : this.outputNeuronId){
            backwardRecursive(NeuronCollection.get(id));
        }
    }

    private void backwardRecursive(Neurons neurons){
        Iterator iterator = neurons.getInputWeights().entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry entry = (Map.Entry) iterator.next();
            Neurons lastNeurons =this.NeuronCollection.get((BigInteger)entry.getKey());
            if (!lastNeurons.getInputWeights().isEmpty()){
                Double totalDeviation = 0d;
                for (BigInteger id : lastNeurons.getOutputs()){
                    Neurons nextNeurons = this.NeuronCollection.get(id);
                    totalDeviation += nextNeurons.getInputDeviation().get(lastNeurons.getId());
                }
                lastNeurons.setTotalDeviation(totalDeviation * lastNeurons.getOutputValue() * (1d-lastNeurons.getOutputValue()));
                lastNeurons.computeDeviation();
                this.NeuronCollection.put(lastNeurons.getId(),lastNeurons);
                backwardRecursive(lastNeurons);
            }
        }
    }

    /**
     * 更新神经网络中每一个神经元的权值和阈值
     */
    public void update(){
        Iterator iterator = this.NeuronCollection.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Neurons neurons = (Neurons) entry.getValue();
            //权值
            Iterator idWeights = neurons.getInputWeights().entrySet().iterator();
            while (idWeights.hasNext()){
                Map.Entry idWeight = (Map.Entry) idWeights.next();
                BigInteger id = (BigInteger) idWeight.getKey();
                Double nowWeight = (Double) idWeight.getValue();
                Double newWeight = nowWeight + 0.9*neurons.getTotalDeviation()*this.getNeuronCollection().get(id).getOutputValue();
                neurons.setInputWeights(id,newWeight);
            }
            //阈值
            neurons.setThreshold(neurons.getThreshold() + 0.9*neurons.getTotalDeviation());
        }
    }
}
